/**
 * area_descriptor.java
 *
 * @author BJ Premore
 * @author Myongsu Choe
 */


package SSF.OS.OSPF;


import java.util.*;
import com.renesys.raceway.DML.*;
import SSF.Net.*;
import SSF.Net.Util.*;
import SSF.OS.BGP4.Util.AS_descriptor;


// ========================= class area_descriptor ========================= //

class area_descriptor {

  /** An area number of -1 indicates that no single area encompasses the
   *  CIDR block in question. */
  public static final int NO_AREA = -1;

  /** Used for assigning unique area numbers.  Hash key is AS number. */
  private static Hashtable NEXT_AVAILABLE_AREA_NUMBER;

  /** A hash table of hash tables, one for each AS, to map Nets to area
   *  numbers within the AS. */
  private static Hashtable area_tables;

  /** Maps AS numbers to booleans, where the boolean indicates whether or not
   *  the area table for the associated AS has been constructed yet.  No area
   *  table is constructed until a request is made from it. */
  private static Hashtable area_table_requested;

  /** Indicates whether or not class data has been initialized. */
  private static boolean initialized;

  /** Returns the number of the area which encloses the given host;
   *  returns NO_AREA if the host is not contained within an area. */
  public synchronized static int get_area_num(Host thehost) {
    // first find out which AS the host is in
    String asnh = AS_descriptor.get_as_nh(thehost);
    int asnum = AS_descriptor.nh2as(asnh);

    if (!initialized) {
      area_tables = new Hashtable();
      area_table_requested = new Hashtable();
      NEXT_AVAILABLE_AREA_NUMBER = new Hashtable();
      initialized = true;
    }

    Boolean requested = (Boolean)area_table_requested.get(new Integer(asnum));
    if (requested == null) {
      Hashtable newtable = new Hashtable();
      build_area_hashtable(asnum, newtable, find_AS_network(thehost));
      area_tables.put(new Integer(asnum), newtable);
      area_table_requested.put(new Integer(asnum), new Boolean(true));
    }
    return ((Integer)((Hashtable)area_tables.get(new Integer(asnum))).
                                 get(thehost.defined_in_network())).intValue();
  }

  /** Builds area_tables by recursively traversing the CIDR tree
   *  of the local AS. */
  private static void build_area_hashtable(int asnum, Hashtable areatable,
                                           cidrBlock forNetwork) {
    areatable.put(forNetwork,
             new Integer(generate_area_number(forNetwork, asnum, areatable))); 
    Enumeration enum;
    for (enum=forNetwork.nhi_children(); enum.hasMoreElements();) {
      build_area_hashtable(asnum, areatable, (cidrBlock)enum.nextElement());
    }
  }

  /** Convenience method to find and return the cidrBlock of the
   *  surrounding Net defining the local AS */
  private static cidrBlock find_AS_network(Host ahost) {
    String str = null;
    cidrBlock blk = ahost.defined_in_network();
    try {
      str = (String)blk.networkConfiguration().findSingle("AS_status");
      while (str == null) {
	blk = blk.nhi_parent();
	if (blk == null) {
	  System.err.println("ERROR: host was not in any AS");
	  System.exit(-1);
	}
	str = (String)blk.networkConfiguration().findSingle("AS_status");
      }
    } catch (configException cfgex) {
      System.err.println("EXCEPTION: error looking for AS_status attribute");
      cfgex.printStackTrace();
    }

    return blk;
  }

  /** Returns an area number for the given CIDR block */
  private static int generate_area_number(cidrBlock blk, int asnum,
                                          Hashtable areatable) {
    String str = null;
    try {
      str = (String)blk.networkConfiguration().findSingle("ospf_area");
      if (str == null) {
	// if we're not at the AS boundary, it's safe to check with parent
	if (blk.networkConfiguration().findSingle("AS_status") == null) {
	  cidrBlock parent = blk.nhi_parent();
	  if (parent == null) { // no parent => top of CIDR hierarchy
	    return NO_AREA;
	  } else {
	    return ((Integer)(areatable.get(parent))).intValue();
	  }
	} else {
	  return NO_AREA;
	}
      } else {
	// Assigning area number by parsing ospf_area attribute (don't
        // just assign them in sequence anymore)
	int area_no = (new Integer((String)str)).intValue();
        Integer I =(Integer)NEXT_AVAILABLE_AREA_NUMBER.get(new Integer(asnum));
        if (I == null) {
          NEXT_AVAILABLE_AREA_NUMBER.put(new Integer(asnum), new Integer(1));
          return 0;
        } else {
          int i = I.intValue();
          NEXT_AVAILABLE_AREA_NUMBER.put(new Integer(asnum), new Integer(i+1));
          return i;
        }
      }
    } catch (configException cfgex) {
      System.err.println("EXCEPTION: error looking for ospf_area attribute");
      cfgex.printStackTrace();
    }
    // this line ought never be reached
    return NO_AREA;
  }


} // end class area_descriptor
